package org.beynet.docs.format;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;

import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.log4j.Logger;
import org.beynet.docs.exceptions.GDocsServerError;
import org.beynet.docs.exceptions.UnexpectedStatusCode;
import org.beynet.docs.helpers.XmlHelper;
import org.beynet.docs.http.GDocsHTTPConstants;
import org.beynet.docs.http.HttpHelper;
import org.beynet.docs.session.Session;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * represent an entry in a document list - ie a part of a collection ; this entry should be :
 * <ul>
 * <li>a file</li>
 * <li>a collection</li>
 * </ul>
 * @author beynet
 *
 */
public class DocumentListEntry {

    public DocumentListEntry(String title,URI contentURI,List<Link> links,DocumentType type,String id) {
        this.title = title ;
        this.contentURI = contentURI ;
        this.links = links ;
        this.type = type ;
        this.id = id ;
    }
    
    public String getId(){
        return(id);
    }
    
    public DocumentType getType() {
        return(type);
    }
    
    public List<Link> getLinks() {
        return links;
    }
    public String getTitle() {
        return title;
    }
    
    /**
     * @return parent entries of this entry - may be empty but will never be null
     */
    public List<Link> getParentLinks() {
        List<Link> result = new ArrayList<Link>();
        for (Link l : getLinks()) {
            if (GDocsHTTPConstants.PARENT_REL.equals(l.getRel())) {
                result.add(l);
            }
        }
        return(result);
    }
    
    /**
     * @throws UnexpectedStatusCode 
     * @throws GDocsServerError 
     * delete current entry
     * @throws IOException 
     * @throws  
     */
    public void delete(Session session) throws IOException, GDocsServerError, UnexpectedStatusCode {
        Link edit = getEditLink();
        logger.info("deleting current entry  - edit link uri="+edit.getURI().toString());
        HttpDelete deleteEntry = HttpHelper.createDeleteRequest(session, edit.getURI().toString()+"?delete=true");
        if (getEtag()!=null) {
            deleteEntry.addHeader("If-Match", getEtag());
        }
        HttpClient client = HttpHelper.getHttpClient();
        HttpResponse resp = client.execute(deleteEntry);
        HttpEntity respEntity = resp.getEntity();
        InputStream io = (respEntity!=null)?respEntity.getContent():null;
        try {
            int status = resp.getStatusLine().getStatusCode();
            if (status!=200) throw new UnexpectedStatusCode(status);
        } finally {
            if (io!=null) io.close();
        }
    }
    
    /**
     * @param rel
     * @return a link with rel equals to provided rel
     */
    public Link getLink(String rel) {
        for (Link l : getLinks()) {
            if (rel.equals(l.getRel())) {
                return(l);
            }
        }
        return(null);
    }
    
    public String getMd5Checksum() {
        return(md5sum);
    }
    
    public String getEtag() {
        return(etag);
    }
    public void setEtag(String etag) {
        this.etag=etag;
    }
    
    public void setMd5Checksum(String md5sum) {
        this.md5sum = md5sum ;
    }
    
    public Link getSelfLink() {
        return(getLink(GDocsHTTPConstants.SELF_REL));
    }
    
    public URI getContentURI() {
        return(this.contentURI);
    }
    
    public Link getEditMediaLink() {
        return(getLink(GDocsHTTPConstants.EDIT_MEDIA_REL_LINK));
    }
    
    public Link getEditLink() {
        return(getLink(GDocsHTTPConstants.EDIT_REL_LINK));
    }
    
    public Link getResumableEditMediaLink(){
        return(getLink(GDocsHTTPConstants.RESUMABLE_UPDATE_REL_LINK));
    }
    public Link getResumableCreateMediaLink(){
        return(getLink(GDocsHTTPConstants.RESUMABLE_UPDATE_REL_LINK));
    }
    
    /**
     * factory method used to make documentlistentry from xml element node
     * @param entryEl
     * @return
     */
    public static DocumentListEntry makeFromEntryNode(Element entryEl) throws GDocsServerError {
     // retrieve all the links of the element
        // -------------------------------------
        NodeList linksNodes;
        try {
            linksNodes = (NodeList) XmlHelper.getXPathInterpretorForGDocsAtom().evaluate("./atom:link", entryEl,XPathConstants.NODESET);
        } catch (XPathExpressionException e) {
            throw new RuntimeException("error evaluating xpath",e);
        }
        List<Link> links = new ArrayList<Link>();
        Link.makeFromNodeList(links, linksNodes);
        
        // title of this entry
        // -------------------
        Element titleNode;
        try {
            titleNode = (Element)XmlHelper.getXPathInterpretorForGDocsAtom().evaluate("./atom:title", entryEl,XPathConstants.NODE);
        } catch (XPathExpressionException e) {
            throw new RuntimeException("error evaluating xpath",e);
        }
        String title = titleNode.getTextContent();
        
        // retrieve content tag
        // --------------------
        String contentURIStr = null ;
        URI contentURI = null ;
        try {
            contentURIStr = XmlHelper.getXPathInterpretorForGDocsAtom().evaluate("./atom:content[@type=\"application/atom+xml;type=feed\"]/@src", entryEl);
            if (contentURIStr!=null) {
                try {
                    contentURI = new URI(contentURIStr);
                } catch (URISyntaxException e) {
                    throw new GDocsServerError("invalid uri found for entry content feed :"+contentURIStr);
                }
            }
        } catch (XPathExpressionException e) {
            
        }
        
        // retrieve entry type and id
        // --------------------------
        Element resourceIdNode;
        try {
            resourceIdNode = (Element)XmlHelper.getXPathInterpretorForGDocsAtom().evaluate("./gd:resourceId", entryEl,XPathConstants.NODE);
        } catch (XPathExpressionException e) {
            throw new RuntimeException("error evaluating xpath",e);
        }
        String id = resourceIdNode.getTextContent();
        if (id==null || "".equals(id)) {
            logger.warn("found an entry with an empty id");
            return(null);
        }
        StringTokenizer st = new StringTokenizer(id,":");
        String type =null;
        if (!st.hasMoreTokens()) {
            logger.warn("found an invalid resourceId");
            return(null);
        }
        type = st.nextToken();
        if (!st.hasMoreTokens()) {
            logger.warn("found an invalid resourceId");
            return(null);
        }
        id = st.nextToken();
        DocumentType typeOfDoc = null ;
        for (DocumentType t : DocumentType.values()){
            if (t.getType().equals(type)) {
                typeOfDoc = t ;
                break;
            }
        }
        if (typeOfDoc==null) {
            logger.warn("found an invalid resourceId");
            return(null);
        }
        DocumentListEntry entry = new DocumentListEntry(title, contentURI, links,typeOfDoc,id);
        
        // retrieve md5sum if found
        Element md5Node;
        try {
            md5Node = (Element)XmlHelper.getXPathInterpretorForGDocsAtom().evaluate("./docs:md5Checksum", entryEl,XPathConstants.NODE);
        } catch (XPathExpressionException e) {
            throw new RuntimeException("error evaluating xpath",e);
        }
        if (md5Node!=null) entry.setMd5Checksum(md5Node.getTextContent());
        
        //retrieve etag
        Node etagNode;
        try {
            etagNode = (Node)XmlHelper.getXPathInterpretorForGDocsAtom().evaluate("./@gd:etag", entryEl,XPathConstants.NODE);
        } catch (XPathExpressionException e) {
            throw new RuntimeException("error evaluating xpath",e);
        }
        if (etagNode!=null) entry.setEtag(etagNode.getTextContent());
        
        
        return(entry);
    }
    
    public static void makeFromNodeList(NodeList entriesNodes,List<DocumentListEntry> entries) throws GDocsServerError {
        for (int i=0;i<entriesNodes.getLength();i++) {
            Element entryEl = (Element)entriesNodes.item(i);
            DocumentListEntry entry = makeFromEntryNode(entryEl);
            if (entry!=null) entries.add(entry);
        }
    }

    private URI contentURI;
    private List<Link> links;
    private String title;
    private DocumentType type ;
    private String id ;
    private String md5sum ;
    private String etag ;
    
    private final static Logger logger = Logger.getLogger(DocumentListEntry.class);
}
